﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using CompactMapper.Orm.Annotations;

namespace CompactMapper.Orm.UnitOfWork.Mappings
{
    internal class TablesFactory
    {
        private readonly Table[] _leaves;
        private readonly Table[] _roots;


        private readonly TablesTree _tablesTree;

        internal TablesFactory(IEnumerable<String> assemblyStrings)
        {
            var allTables = GetTablesFromAssemblies(assemblyStrings).ToArray();
            _tablesTree = new TablesTree(allTables);
            _leaves = allTables.Where(table => table.Meta.Leaf).ToArray();
            _roots = GetRoots(allTables.Except(_leaves)).ToArray();
            BuildTablesTree(_tablesTree, _roots, _leaves);
        }

        public TablesTree TablesTree
        {
            get { return _tablesTree; }
        }


        private static IEnumerable<Table> GetTablesFromAssemblies(IEnumerable<String> assemblyStrings)
        {
            return
                assemblyStrings.Select(assembly => Assembly.Load(assembly).GetTypes()).
                    SelectMany(type => type).
                    Where(type => Attribute.IsDefined(type, typeof (TableAttribute))).
                    Select(type => new Table(type));
        }

        private static IEnumerable<Table> GetRoots(IEnumerable<Table> tables)
        {
            var allRelatedTableNames = tables.Select(table => table.Meta.RelatedTableNames).SelectMany(x => x);
            var roots = tables.Where(table => !allRelatedTableNames.Contains(table.Name));
            return roots;
        }

        private static void BuildTablesTree(TablesTree tablesTree, IEnumerable<Table> roots, IEnumerable<Table> leaves)
        {
            tablesTree.AddRange(roots.Select(root => BuildTree(root, tablesTree)));
            AddMissingLeaves(tablesTree, leaves);
        }

        private static void AddMissingLeaves(Tree<Table> tree, IEnumerable<Table> leaves)
        {
            foreach (var leaf in leaves)
            {
                if (tree.Contains(leaf) == false)
                {
                    tree.Add(new TablesTree(leaf));
                }
            }
        }

        private static Tree<Table> BuildTree(Table root, TablesTree tablesTree)
        {
            var treeItem = new TablesTree(root);
            treeItem.AddRange(GetChildren(root, tablesTree).Select(child => BuildTree(child, tablesTree)));
            return treeItem;
        }

        private static IEnumerable<Table> GetChildren(Table root, TablesTree tablesTree)
        {
            var children = new List<Table>();
            foreach (var tableName in root.Meta.RelatedTableNames)
            {
                var child = tablesTree.GetTable(tableName);
                child.AddRelationship(root);
                children.Add(child);
            }
            return children;
        }
    }
}